'''Projectiles module.

Defines a generic projectile and the specialized projectiles in the game.'''

from locals import *
from simple_math import *
from effect import *

from math import cos, sin, radians
from pygame import draw, rect

class Projectile:
    '''This class represents a generic projectile. It stores information such
    as orientation, position, speed and the size of the projectile. No instances
    of this specific class should be created, instead of that one should
    specialize this class and instance from the specialized one.
    '''
    def __init__(self, position, orientation, speed, radius, p_type, effect_class):
        '''"Launches" a projectile from the given position, in the given
        orientation and with the given speed.

        Note that the function adjusts the position of the projectile according
        to the mage's size.
        TODO: Change the position update. Give it a "initial shift" parameter
        instead.

        position:     The position of the source of the projectile.
        orientation:  The inital orientation of the projectile.
        type:         Describes which kind of projectile it is (i.e.: FIREBALL)
        effect_class: A reference to the special effects class. This affects
            the visual and audio effects caused by the projectile's contact
            with a solid object.'''
        self.position     = (position[0]+cos(orientation)*(radius+MAGE_RADIUS),
                             position[1]+sin(orientation)*(radius+MAGE_RADIUS))
        self.orientation  = orientation
        self.speed        = speed
        self.radius       = radius
        self.rect         = None
        self.type         = p_type
        self.destroy      = False
        self.effect_class = effect_class

    ##############
    # Properties #
    ##############
    @property
    def iPos(self):
        (x, y) = self.position
        return (int(round(x)), int(round(y)))

    def burst(self):
        '''Some projectiles may launch another group of projectiles when
        exploding. If that is the case, then this method should be specialized
        and made to return a list of projectiles that are generated during the
        explosion.'''
        return []

    def deflect(self, orientation):
        self.orientation = orientation
    
    def draw(self, surface):
        '''This method should always be specialized. It draws the projectile
        in the given surface.'''
        pass
    
    def get_next_position(self):
        '''Calculates the next position of the projectile if nothing should
        disturb it's route.'''
        xd = self.speed * cos(self.orientation)
        yd = self.speed * sin(self.orientation)
        return (self.position[0]+xd, self.position[1]+yd)

    def get_rect(self):
        return self.rect

    def hit_mage(self, mage):
        mage.hit(self.type)
        print "Hitting with type", self.type
        self.destroy = True

    def hit_wall(self, A, B):
        self.destroy = True
    
    def hit_wall_pos(self, A, B, d):
        line_angle = atan2(B[1]-A[1], B[0]-A[0])
        theta      = fabs(self.orientation - line_angle)
        if theta > 0.2:
            m = (d - self.radius)/sin(theta)
        else:
            m = d - self.radius
        return (self.position[0] + m*cos(self.orientation),
                self.position[1] + m*sin(self.orientation))
    
    def move(self, position):
        '''Updates the position of the projectile. Does not consider collision.
        '''
        self.position = position
    
    def reflect(self, A, B):
        '''Reflects the projectile's direction given a line formed by the
        points A and B.'''
        self.speed = -self.speed

class Fireball(Projectile):
    def __init__(self, position, orientation):
        Projectile.__init__(self, position, orientation, FIREBALL_SPEED,
                            FIREBALL_RADIUS, FIREBALL, Fireball_Effect)

    def burst(self):
        result = []
        for angle in xrange(0, 12):
            result.append(
                Fireball_B(self.position,
                           self.orientation+radians(30*angle))
            )
        return result

    def draw(self, surface):
        self.rect = draw.circle(surface, BLACK, self.iPos, FIREBALL_RADIUS+2)
        draw.circle(surface, FIREBALL_COLOR, self.iPos, FIREBALL_RADIUS)

class Fireball_B(Projectile):
    def __init__(self, position, orientation):
        Projectile.__init__(self, position, orientation, FIREBALL_B_SPEED,
                            FIREBALL_B_RADIUS, FIREBALL, Fireball_Effect)
    
    def draw(self, surface):
        self.rect = draw.circle(surface, BLACK, self.iPos, FIREBALL_B_RADIUS+2)
        draw.circle(surface, FIREBALL_B_COLOR, self.iPos, FIREBALL_B_RADIUS)

class Electric_Bolt(Projectile):
    def __init__(self, position, orientation):
        Projectile.__init__(self, position, orientation, E_BOLT_SPEED,
                            E_BOLT_RADIUS, E_BOLT, E_Bolt_Effect)
        self.bounces_left = 4
        
    def draw(self, surface):
        self.rect = draw.circle(surface, BLACK, self.iPos, E_BOLT_RADIUS+2)
        draw.circle(surface, E_BOLT_COLOR, self.iPos, E_BOLT_RADIUS)
    
    def hit_wall(self, A, B):
        if self.bounces_left:
            self.orientation = reflect_angle(A, B, self.orientation)
            self.bounces_left -= 1
        else:
            self.destroy = True

class Magic_Missile(Projectile):
    def __init__(self, position, orientation, target):
        Projectile.__init__(self, position, orientation, MMISSILE_SPEED,
                            MMISSILE_RADIUS, MMISSILE, MMissile_Effect)
        self.target    = target
        self.tail      = []

    def draw(self, surface):
        rects = []
        radius = MMISSILE_TAILRADIUS
        for pos in self.tail:
            rects.append(draw.circle(surface, BLACK, pos, radius+2))
            draw.circle(surface, MMISSILE_COLOR, pos, radius)
            radius+=1
        self.rect = draw.circle(surface, BLACK, self.iPos, MMISSILE_RADIUS+2)
        self.rect = self.rect.unionall(rects)
        draw.circle(surface, MMISSILE_COLOR, self.iPos, MMISSILE_RADIUS)

    def get_rect(self):
        return self.rect

    def move(self, position):
        self.tail.append(self.iPos)
        if len(self.tail) > MMISSILE_TAILLENGTH:
            self.tail = self.tail[1:]

        Projectile.move(self, position)
        xd = self.target.position[0] - self.position[0]
        yd = self.target.position[1] - self.position[1]
        orientation = atan2(yd, xd)
        if orientation < 0:
            orientation += pi_x_2
        if self.orientation < 0:
            self.orientation += pi_x_2
        thetad = orientation - self.orientation
        if thetad < 0:
            thetad += pi_x_2
        if fabs(thetad) > 0.1:
            if (thetad > 0.1 and thetad < pi) :
                self.orientation += MMISSILE_TURN_SPEED
            else:
                self.orientation -= MMISSILE_TURN_SPEED

